﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Mbs.Utils.ImageHelper
{
    /// <summary>
    /// 用于图像分色
    /// </summary>
    public class ExtractImageColor
    {

        /// <summary>
        /// 定义分色的色块
        /// </summary>
        /// <param name="imgPath"></param>
        /// <param name="delta"></param>
        /// <param name="count">0代表</param>
        /// <param name="reduce_brightness"></param>
        /// <param name="reduce_gradients"></param>
        public Dictionary<Color, float> ParseColor(Bitmap map,
            int delta = 16,
            int count = 0,
            bool reduce_brightness = true,
            bool reduce_gradients = true
            )
        {
            int half_delta = 0;
            if (delta > 2)
                half_delta = delta / 2 - 1;

            var PREVIEW_WIDTH = 150f;
            var PREVIEW_HEIGHT = 150f;

            double width = map.Width;
            double height = map.Height;
            var scale = Math.Min(PREVIEW_WIDTH / map.Width, PREVIEW_HEIGHT / map.Height);
            if (scale < 1)
            {
                width = Math.Floor(scale * map.Width);
                height = Math.Floor(scale * map.Height);
            }
            var mapNew = new Bitmap(map, (int)width, (int)height);

            var hexarray = new Dictionary<Color, int>();
            var total_pixel_count = 0;
            for (int y = 0; y < mapNew.Height; y++)
            {
                for (int x = 0; x < mapNew.Width; x++)
                {
                    total_pixel_count++;
                    var color = mapNew.GetPixel(x, y);
                    var newcolor = new int[] { 0, 0, 0 };
                    if (delta > 1)
                    {
                        newcolor[0] = ((color.R + half_delta) / delta).toint32() * delta;
                        newcolor[1] = ((color.G + half_delta) / delta).toint32() * delta;
                        newcolor[2] = ((color.B + half_delta) / delta).toint32() * delta;
                        newcolor[0] = Math.Min(255, newcolor[0]);
                        newcolor[1] = Math.Min(255, newcolor[1]);
                        newcolor[2] = Math.Min(255, newcolor[2]);
                    }

                    var _color = Color.FromArgb(newcolor[0], newcolor[1], newcolor[2]);
                    if (!hexarray.ContainsKey(_color))
                    {
                        hexarray[_color] = 1;
                    }
                    else
                    {
                        hexarray[_color]++;
                    }
                }
            }
            if (mapNew != null) mapNew.Dispose();

            if (reduce_gradients)
            {
                // if you want to *eliminate* gradient variations use:
                // ksort( & hexarray );
                var arr = from o in hexarray orderby o.Value ascending select o;

                var gradients = new Dictionary<Color, Color>();
                foreach (var hex in arr)
                {
                    Color new_hex = new Color();
                    if (!gradients.ContainsKey(hex.Key))
                    {
                        new_hex = _find_adjacent(hex.Key, gradients, delta);
                        gradients[hex.Key] = new_hex;
                    }
                    else
                    {
                        new_hex = gradients[hex.Key];
                    }

                    if (hex.Key != new_hex)
                    {
                        hexarray[hex.Key] = 0;
                        hexarray[new_hex] += hex.Value;
                    }
                }
            }

            if (reduce_brightness)
            {
                // if you want to *eliminate* brightness variations use:
                // ksort( & hexarray );
                var arr = from o in hexarray orderby o.Value ascending select o;
                var brightness = new Dictionary<Color, Color>();
                foreach (var hex in arr)
                {
                    Color new_hex = new Color();
                    if (!brightness.ContainsKey(hex.Key))
                    {
                        new_hex = _normalize(hex.Key, brightness, delta);
                        brightness[hex.Key] = new_hex;
                    }
                    else
                    {
                        new_hex = brightness[hex.Key];
                    }
                    if (hex.Key != new_hex)
                    {
                        hexarray[hex.Key] = 0;
                        hexarray[new_hex] += hex.Value;
                    }
                }
            }

            var ARR = from o in hexarray orderby o.Value descending select o;

            var hexarrayP = new Dictionary<Color, float>();
            // convert counts to percentages
            foreach (var key in ARR)
            {
                hexarrayP[key.Key] = (float)key.Value / total_pixel_count;
            }

            if (count == 0) count = hexarrayP.Count;


            for (int i = 0; i < count; i++)
            {

            }

            return hexarrayP;
        }

        private Color _normalize(Color color, Dictionary<Color, Color> brightness, int delta)
        {
            var lowest = 255;
            var highest = 0;

            lowest = Math.Min(Math.Min(Math.Min(lowest, color.R), color.G), color.B);
            highest = Math.Max(Math.Max(Math.Max(highest, color.R), color.G), color.B);


            // Do not normalize white, black, or shades of grey unless low delta
            if (lowest == highest)
            {
                if (delta <= 32)
                {
                    if (lowest == 0 || highest >= (255 - delta))
                    {
                        return color;
                    }
                }
                else
                {
                    return color;
                }
            }

            for (; highest < 256; lowest += delta, highest += delta)
            {
                try
                {
                    if (color.R - lowest < 0) break;
                    if (color.G - lowest < 0) break;
                    if (color.B - lowest < 0) break;

                    Color new_hex = Color.FromArgb(color.R - lowest, color.G - lowest, color.B - lowest);
                    if (brightness.ContainsKey(new_hex))
                    {
                        // same color, different brightness - use it instead
                        return new_hex;
                    }
                }
                catch { }
            }
            return color;
        }

        private Color _find_adjacent(Color hex, Dictionary<Color, Color> gradients, int delta)
        {
            if (hex.R > delta)
            {
                Color tmp = Color.FromArgb(hex.R - delta, hex.G, hex.B);
                if (gradients.ContainsKey(tmp))
                    return gradients[tmp];
            }
            if (hex.G > delta)
            {
                Color tmp = Color.FromArgb(hex.R, hex.G - delta, hex.B);
                if (gradients.ContainsKey(tmp))
                    return gradients[tmp];
            }
            if (hex.B > delta)
            {
                Color tmp = Color.FromArgb(hex.R, hex.G, hex.B - delta);
                if (gradients.ContainsKey(tmp))
                    return gradients[tmp];
            }

            if (hex.R < (255 - delta))
            {
                Color tmp = Color.FromArgb(hex.R + delta, hex.G, hex.B);
                if (gradients.ContainsKey(tmp))
                    return gradients[tmp];
            }

            if (hex.G < (255 - delta))
            {
                Color tmp = Color.FromArgb(hex.R, hex.G + delta, hex.B);
                if (gradients.ContainsKey(tmp))
                    return gradients[tmp];
            }

            if (hex.B < (255 - delta))
            {
                Color tmp = Color.FromArgb(hex.R, hex.G, hex.B + delta);
                if (gradients.ContainsKey(tmp))
                    return gradients[tmp];
            }

            return hex;
        }
    }
}
